package com.kedacom.ctsp.authz.aspect;

import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
import com.kedacom.ctsp.authz.AuthenticationService;
import com.kedacom.ctsp.authz.Authorize;
import com.kedacom.ctsp.authz.access.DataAccessHandler;
import com.kedacom.ctsp.authz.entity.AuthContext;
import com.kedacom.ctsp.authz.entity.AuthResource;
import com.kedacom.ctsp.authz.entity.Authentication;
import com.kedacom.ctsp.authz.entity.AuthzTypeEnum;
import com.kedacom.ctsp.authz.exception.NoPermissionAccessException;
import com.kedacom.ctsp.lang.ArrayUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Set;

/**
 * AuthorizeService的默认实现类.
 *
 * @author xuwei
 * @create 2017-12-01 10:44
 **/
@Slf4j
public class AuthorizeServiceImpl implements AuthorizeService {

    @Autowired
    protected List<DataAccessHandler> dataAccessHandlers;

    @Autowired
    protected AuthenticationService authenticationService;

    @PostConstruct
    public void init() {
        if (CollectionUtils.isEmpty(dataAccessHandlers) && log.isWarnEnabled()) {
            log.warn("DataAccessHandler is blank. DataAccess Authorized will not work!!!");
        }
    }

    @Override
    public void handle(AuthContext context) {
        Authentication authentication = context.getAuthentication();

        Authorize classAnno = context.getParamContext().getClassAnnotation(Authorize.class);
        Authorize methodAnno = context.getParamContext().getMethodAnnotation(Authorize.class);
        Authorize anno = context.getParamContext().getAnnotation(Authorize.class);

        // 获取类+方法上的roles
        Set<String> roles = getUnionRoles(classAnno, methodAnno);

        Set<String> resources = getInheritResources(classAnno, methodAnno);

        if (CollectionUtils.isNotEmpty(roles) && !CollectionUtils.containsAny(authentication.getRoleSigns(), roles)) {
            throw new NoPermissionAccessException(anno.message());
        }

        if (CollectionUtils.isNotEmpty(resources)) {
            // server端直接从数据库取；client端添加一个实现，从redis中取，如果redis没有，通过接口从servier端获取
            List<AuthResource> authResources = authenticationService.loadResources(authentication, resources, AuthzTypeEnum.ANNOTATION);
            if (CollectionUtils.isEmpty(authResources)) {
                throw new NoPermissionAccessException(anno.message());
            }
            if (dataAccessAvailable(anno, resources)) {
                boolean authorized = false;
                // 没有resource返回
                if (CollectionUtils.isNotEmpty(authResources)) {
                    // 有resource 继续进行dataScope DataAccess fileds的控制
                    authorized = dataAccessHandlers.stream().allMatch(handler -> handler.handle(authentication, authResources, anno, context.getParamContext()));
                }
                if (!authorized) {
                    throw new NoPermissionAccessException(anno.message());
                }
            }
        }

    }

    private boolean dataAccessAvailable(Authorize anno, Set<String> resources) {
        return anno.dataAccess() && CollectionUtils.isNotEmpty(resources) && CollectionUtils.isNotEmpty(dataAccessHandlers);
    }

    /**
     * 1. 如果classAnno不为空, methodAnno为空,使用classAnno上面的resources
     * 2. 如果classAnno为空, methodAnno不为空, 使用methodAnno上面的resources
     * 3. 如果classAnno不为空, methodAnno不为空, 使用classResource_methodResource的组合方式作为resources.
     *
     * @param classAnno
     * @param methodAnno
     * @return
     */
    private Set<String> getInheritResources(Authorize classAnno, Authorize methodAnno) {
        Set<String> resources = Sets.newHashSet();

        if (methodAnno == null || ArrayUtil.isEmpty(methodAnno.resources())) {
            CollectionUtils.addAll(resources, classAnno.resources());
        } else if (classAnno == null || ArrayUtil.isEmpty(classAnno.resources())) {
            CollectionUtils.addAll(resources, methodAnno.resources());
        } else {
            for (String classResource : classAnno.resources()) {
                for (String methodResource : methodAnno.resources()) {
                    resources.add(Joiner.on("_").join(classResource, methodResource));
                }
            }
        }
        return resources;
    }

    private Set<String> getUnionRoles(Authorize classAnno, Authorize methodAnno) {
        Set<String> roles = Sets.newHashSet();
        if (classAnno != null && classAnno.roles() != null) {
            CollectionUtils.addAll(roles, classAnno.roles());
        }
        if (methodAnno != null && methodAnno.roles() != null) {
            CollectionUtils.addAll(roles, methodAnno.roles());
        }
        return roles;
    }
}
